// Copyright (c) 2009 Cloudage

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
// ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
// OR OTHER DEALINGS IN THE SOFTWARE.

#include "stdafx.h"
#include "luabindSensor.h"
#include "luabind_detail_class_registry_hack.h"
#include "luabind_detail_class_rep_hack.h"
#include "luabind_detail_function.h"
#include "luabind_getupvalue_fix.h"
#include <luabind/class_info.hpp>
#include <luabind/version.hpp>

#if (LUA_VERSION_NUM!=501)
#error Need lua 5.1
#endif

#if (LUABIND_VERSION!=801)
#error Need luabind 8.1
#endif


//send a instance

// void GetClasses(lua_State *l)
// {
// 	using namespace luabind::detail;
// 	typedef std::map<LUABIND_TYPE_INFO, class_rep*,class_registry_hack::cmp> ClassMap;
// 	class_registry_hack * hack = (class_registry_hack*)class_registry::get_registry(l);
// 	const ClassMap& cm = hack->get_classes();
// 	ClassMap::const_iterator end=cm.end();
// 	std::vector<class_rep*> reps;	//all class reps here
// 	std::vector<std::string> names;	//for test,get all class names.
// 	for(ClassMap::const_iterator i = cm.begin();i!=end;i++)
// 	{
// 		reps.push_back(i->second);
// 		names.push_back(i->second->name());
// 	}
// 	
// }

using namespace LuabindSensor;

struct FindInfoByObjectIndex
{
	int target;
	FindInfoByObjectIndex(int index):target(index)
	{

	}
	bool operator()(Info::SharePtr i)
	{
		return i->address==target;
	}
};

unsigned int GetObjectIndex(const luabind::object& obj)
{
	lua_State *l = obj.interpreter();
	obj.push(l);
	unsigned int pointer = (unsigned int)lua_topointer(l,-1);
	lua_pop(l,1);
	return pointer;
}

std::string LuabindSensor::GetLuaTypeString(int luaType)
{
	switch(luaType)
	{
		case LUA_TNIL:return "Nil";
		case LUA_TBOOLEAN:return "Boolean";
		case LUA_TLIGHTUSERDATA:return "LightUserData";
		case LUA_TNUMBER:return "Number";
		case LUA_TSTRING:return "String";
		case LUA_TTABLE:return "Table";
		case LUA_TFUNCTION:return "Function";
		case LUA_TUSERDATA:return "UserData";
		case LUA_TTHREAD:return "Thread";
		default:return "?";
	}
}

LuabindSensor::FunctionInfo::SharePtr LuabindSensor::CreateFunctionDetail(const std::string& key,const luabind::object& functionObj)
{	
	FunctionInfo::SharePtr fi = FunctionInfo::SharePtr(new FunctionInfo());
	fi->name = key;	
	fi->address = GetObjectIndex(functionObj);	
    fi->obj = functionObj;

	luabind::object uv = luabind::getupvalue_fix(functionObj,1);
	luabind::detail::function *fun = luabind::touserdata<luabind::detail::function>(uv);
	if(fun!=0)
	{		
		lua_State *l = functionObj.interpreter();	
		fun->m_signature(l,fun->m_name.c_str());	
		fi->signature = lua_tostring(l,-1);	
		lua_pop(l,1);
	}

	return fi;
}

LuabindSensor::PropertyInfo::SharePtr LuabindSensor::CreatePropertyDetail(const std::string& key,const luabind::object& propertyObj)
{	
	PropertyInfo::SharePtr pi = PropertyInfo::SharePtr(new PropertyInfo());

	pi->name=key;
	pi->address=GetObjectIndex(propertyObj);
    pi->obj = propertyObj;

	lua_State *l = propertyObj.interpreter();	

	luabind::object getter = luabind::getupvalue_fix(propertyObj,1);
	luabind::detail::function *fun = luabind::touserdata<luabind::detail::function>(luabind::getupvalue_fix(getter,1));	
	fun->m_signature(getter.interpreter(),fun->m_name.c_str());	
	std::string sig_getter = lua_tostring(l,-1);	
	lua_pop(l,1);
		
	pi->typeName = sig_getter.substr(0,sig_getter.find('('));
	boost::trim(pi->typeName);

	luabind::object setter = luabind::getupvalue_fix(propertyObj,2);	
	pi->isReadOnly = luabind::type(setter)==LUA_TNIL;//luabind::upvalue_not_exists(setter,1);
	
	return pi;
}

LuabindSensor::ClassInfo::SharePtr LuabindSensor::CreateClassDetail(const std::string& keystr,const luabind::object& classDefObj,ClassContext& cc)
{	
	lua_State* l =classDefObj.interpreter();
	luabind::detail::class_rep *crep = luabind::touserdata<luabind::detail::class_rep>(classDefObj);
	
	ClassInfo::SharePtr ci;
	ClassContext::iterator iter = cc.find((unsigned int)crep);
	if(iter==cc.end())
	{
		ci = ClassInfo::SharePtr(new ClassInfo());
		cc[(unsigned int)crep]=ci;
	}
	else
	{
		ci=iter->second;
	}

	ci->name=keystr;	
	ci->address=GetObjectIndex(classDefObj);	
    ci->obj = classDefObj;
	
	ci->cppName=crep->name();
	ci->isCppClass = crep->get_class_type()==luabind::detail::class_rep::cpp_class;

	//members
	crep->get_table(l);	
	luabind::object table(luabind::from_stack(l, -1));
	lua_pop(l,1);
	for (luabind::iterator i(table), end; i != end; ++i)
	{
		luabind::object key =i.key();					
		const luabind::object& value = *i;
		if(luabind::type(value)==LUA_TFUNCTION)
		{
			if(IsProperty(value))
			{
				if(!luabind::upvalue_not_exists(value,1))
				{					
					PropertyInfo::SharePtr pi = CreatePropertyDetail(luabind::object_cast<std::string>(key),value);
					ci->properties.push_back(pi);
				}
			}
			else
			{				
				FunctionInfo::SharePtr fi = CreateFunctionDetail(luabind::object_cast<std::string>(key),value);
				ci->methods.push_back(fi);
			}
		}
	}

	//static members
	crep->get_default_table(l);
	luabind::object defaultTable(luabind::from_stack(l, -1));
	lua_pop(l,1);
	for (luabind::iterator i(defaultTable), end; i != end; ++i)
	{
		luabind::object key =i.key();					
		const luabind::object& value = *i;
		if(luabind::type(value)==LUA_TFUNCTION)
		{
			LuabindSensor::FunctionInfo::SharePtr fi = CreateFunctionDetail(luabind::object_cast<std::string>(key),value);			
			ci->staticMethods.push_back(fi);
		}
	}

	//enums
	{
		using namespace luabind::detail;
		class_rep_hack *hack = (class_rep_hack *)crep;

		const class_rep_hack::StaticConstantsMap& map = hack->GetStaticConstants();

		for(class_rep_hack::StaticConstantsMap::const_iterator i = map.begin();i!=map.end();i++)
		{			
			const char* key = i->first;
			int value = i->second;
			ci->enums.push_back(LuabindSensor::ClassInfo::EnumItem(key,value));
		}
	}

	return ci;
}

LuabindSensor::TableInfo::SharePtr LuabindSensor::CreateTableDetail(const std::string& keystr,const luabind::object& tableObj,TableContext& tc,ClassContext& cc)
{
	int objIndex = GetObjectIndex(tableObj);
	TableContext::iterator tableIter = tc.find(objIndex);
	TableInfo::SharePtr ti;
	if(tableIter==tc.end())
	{
		ti = TableInfo::SharePtr(new TableInfo());
		tc[objIndex]=ti;	
	}
	else
		ti = tableIter->second;

	ti->name=keystr;	
	ti->address=GetObjectIndex(tableObj);
    ti->obj = tableObj;
	

	for(luabind::iterator i(tableObj),end;i!=end;i++)
	{
		luabind::object key =i.key();		
		std::string keyName;
		switch(luabind::type(key))
		{
		case LUA_TSTRING:
			keyName = luabind::object_cast<std::string>(key);
			break;
		case LUA_TNUMBER:
			{
				std::stringstream ss;
				ss<<luabind::object_cast<double>(key);
				keyName=ss.str();
			}
			break;
		default:
			keyName="?";
			break;
		}
		const luabind::object& value = *i;

		switch(luabind::type(value))
		{
		case LUA_TTABLE:
			{
				TableContext::iterator iter = tc.find(GetObjectIndex(value));
				if(iter==tc.end())												
					ti->subTables.push_back(CreateTableDetail(keyName,value,tc));
				else
					ti->referenceTables.push_back(iter->second);
			}
			break;
		case LUA_TUSERDATA:
			{	
				if(IsClassDefinition(value))
				{
					ti->classes.push_back(CreateClassDetail(keyName,value,cc));
				}
				else
				{	
					ti->others.push_back(CreateObjectDetail(keyName,value,cc));
				}				
			}
			break;
		case LUA_TFUNCTION:
			{
				ti->functions.push_back(CreateFunctionDetail(keyName,value));
			}
			break;		
		default:
			{	
				ti->others.push_back(CreateObjectDetail(keyName,value,cc));
			}
			break;
		}
	}

	return ti;
}

LuabindSensor::TableInfo::SharePtr LuabindSensor::CreateAllDetail(lua_State* l)
{
	return CreateTableDetail("_G",luabind::globals(l));
}

Info::SharePtr LuabindSensor::CreateDetail( luabind::object& obj )
{
	switch(luabind::type(obj))
	{
	case LUA_TTABLE:
		return static_pointer_cast<Info>(CreateTableDetail("",obj));
	case LUA_TUSERDATA:
		{	
			if(IsClassDefinition(obj))
			{
				return CreateClassDetail("",obj);
			}
			else
			{	
				return CreateObjectDetail("",obj);
			}				
		}
		break;
	case LUA_TFUNCTION:
		{
			if(IsProperty(obj))
				return CreatePropertyDetail("",obj);
			else
				return CreateFunctionDetail("",obj);
		}
		break;		
	default:
		{	
			return CreateObjectDetail("",obj);
		}
		break;
	}
}

ObjectInfo::SharePtr LuabindSensor::CreateObjectDetail( const std::string& keystr,const luabind::object& value,ClassContext& cc/*=ClassContext()*/ )
{
	ObjectInfo::SharePtr obj = ObjectInfo::SharePtr(new ObjectInfo);

	obj->address=GetObjectIndex(value);
	obj->name=keystr;				
	obj->luaType=GetLuaTypeString(luabind::type(value));
    obj->obj = value;

	switch(luabind::type(value))
	{
	case LUA_TUSERDATA:
		{			
			lua_State *l = value.interpreter();
			value.push(l);
			luabind::detail::object_rep *orep = luabind::detail::is_class_object(l,-1);
			if(orep!=0)
			{
				luabind::detail::class_rep *crep = orep->crep();
				ClassContext::iterator iter = cc.find((unsigned int)crep);
				if(iter==cc.end())
				{
					ClassInfo::SharePtr ci = ClassInfo::SharePtr(new ClassInfo());
					ci->name=crep->name();
					cc[(unsigned int)crep]=ci;
					obj->type=ci;							
				}
				else
				{
					obj->type=iter->second;
				}
			}
			lua_pop(l,1);
		}
		break;
	case LUA_TBOOLEAN:
		{			
			obj->value=luabind::object_cast<bool>(value)?"true":"false";		
		}
		break;
	case LUA_TSTRING:
		{			
			obj->value=luabind::object_cast<std::string>(value);		
		}
		break;
	case LUA_TNUMBER:
		{			
			std::stringstream ss;
			ss<<luabind::object_cast<double>(value);
			obj->value=ss.str();		
		}
		break;
	default:
		obj->value="?";
		break;
	}

	return obj;
}

bool LuabindSensor::IsClassDefinition( const luabind::object& obj )
{
	lua_State* l = obj.interpreter();
	obj.push(l);
	bool isClassRep = luabind::detail::is_class_rep(l,-1);
	lua_pop(l,1);
	return isClassRep;
}

bool LuabindSensor::IsProperty( const luabind::object& obj )
{
	if(luabind::type(obj)!=LUA_TFUNCTION)return false;
	lua_State* l = obj.interpreter();
	obj.push(l);
	bool isProperty = lua_tocfunction(l,-1)==&luabind::detail::property_tag;
	lua_pop(l,1);
	return isProperty;
}